package me.zhengjie.modules.security.provider;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.security.service.UserDetailsServiceImpl;
import me.zhengjie.modules.security.service.dto.JwtUserDto;
import me.zhengjie.security.authentication.CorporationAuthenticationToken;
import me.zhengjie.security.corporationdetails.CorporationDetails;
import me.zhengjie.security.corporationdetails.CorporationDetailsService;
import me.zhengjie.utils.StringUtils;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * 带企业code认证的授权
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CorporationAuthProvider implements AuthenticationProvider {

    private final CorporationDetailsService corporationDetailsService;

    private final UserDetailsService userDetailsService;

    private final PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        CorporationAuthenticationToken token = (CorporationAuthenticationToken) authentication;

        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();

        String corporationCode = token.getCorporation() == null ? "NONE" : token.getCorporationCode();

        CorporationDetails corporationDetails = corporationDetailsService.findByCorporationCode(corporationCode);

        if (corporationDetails == null) {
            throw new UsernameNotFoundException("集团不存在：" + corporationCode);
        }

        UserDetails user = userDetailsService.loadUserByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException("用户不存在：" + username);
        }

        this.preAuthenticationChecks(user);
        this.checkPassword(authentication, user);

        if (!"admin".equals(username)) {
            JwtUserDto jwtUserDto = UserDetailsServiceImpl.userDtoCache.get(username);
            String userCorporationCode = jwtUserDto.getUser().getCorporationCode();
            if (StringUtils.isNotEmpty(userCorporationCode) && !userCorporationCode.equals(corporationCode)){
                throw new UsernameNotFoundException("用户绑定集团：" + userCorporationCode);
            }
        }

        CorporationAuthenticationToken result = new CorporationAuthenticationToken(
                user, authentication.getCredentials(), corporationDetails, user.getAuthorities());
        result.setDetails(authentication.getDetails());

        return result;
    }


    private void checkPassword(Authentication authentication, UserDetails userDetails) {
        String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            log.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException("密码错误");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return CorporationAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public void preAuthenticationChecks(UserDetails user) {
        if (!user.isAccountNonLocked()) {
            log.debug("User account is locked");

            throw new LockedException("用户被锁定");
        }

        if (!user.isEnabled()) {
            log.debug("User account is disabled");

            throw new DisabledException("用户不可用");
        }

        if (!user.isAccountNonExpired()) {
            log.debug("User account is expired");

            throw new AccountExpiredException("用户已失效");
        }
    }

}
